//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  The core libraries for the MapWindow 6.0 project.
//
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is from MapWindow.dll version 6.0
//
// The Initial Developer of this Original Code is Ted Dunsford. Created 2/21/2008 9:28:29 AM
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using MapWindow.Main; // ProgressHandler
using MapWindow.Components; // mwStatusStrip
using MapWindow.DirectX;
using MapWindow.DirectX.PluginInterfaces;
using MapWindow.Drawing;
namespace MapWindow.DirectX.Components
{
    /// <summary>
    /// This can be used as a component to work as a LayerManager.  This also provides the
    /// very important DefaultLayerManager property, which is where the developer controls
    /// what LayerManager should be used for their project.
    /// </summary>
    [ToolboxBitmap("LayerManager.ico")]
    public class LayerManagerDX : Component, ILayerManagerDX
    {

        #region Private Variables

        // If this doesn't exist, a new one is created when you "get" this data manager.
        private static ILayerManagerDX _defaultLayerManager;
        private System.ComponentModel.IContainer components = null; //Required designer variable.
        private List<ILayerProviderDX> _layerProviders;
        private IList<ILayerDX> _activeProjectLayers;

        /// <summary>
        /// Gets or sets a temporary list of active project layers.  This is designed to house 
        /// the layers from a map frame when the property grids are shown for layers in that
        /// map frame.  This list on the DefaultLayerManager is what is used to create the
        /// list that populates dropdowns that can take a Layer as a parameter.
        /// </summary>
        public IList<ILayerDX> ActiveProjectLayers
        {
            get { return _activeProjectLayers; }
            set { _activeProjectLayers = value; }
        }
        
        



        private string _dialogReadFilter;
        private string _dialogWriteFilter;
       


        private IProgressHandler _progressHandler;
        
        private System.Collections.Generic.Dictionary<string, ILayerProviderDX> _preferredProviders;

        private List<string> _layerProviderDirectories;

        private bool _loadInRam = true;

       
        
        /// <summary>
        /// Gets or sets the implemenation of ILayerManager for the project to use when
        /// accessing data.  This is THE place where the LayerManager can be replaced
        /// by a different data manager.  If you add this data manager to your
        /// project, this will automatically set itself as the DefaultLayerManager.
        /// However, since each DM will do this, you may have to control this manually
        /// if you add more than one LayerManager to the project in order to set the
        /// one that will be chosen.
        /// </summary>
        public static ILayerManagerDX DefaultLayerManager
        {
            get 
            {
                if (LayerManagerDX._defaultLayerManager == null)
                {
                    _defaultLayerManager = new LayerManagerDX();
                }
                return LayerManagerDX._defaultLayerManager;

            }
            set 
            {
                LayerManagerDX._defaultLayerManager = value;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of the LayerManager class.  A data manager is more or less
        /// just a list of data providers to use.  The very important 
        /// LayerManager.DefaultLayerManager property controls which LayerManager will be used
        /// to load data.  By default, each LayerManager sets itself as the default in its
        /// constructor.
        /// </summary>
        public LayerManagerDX()
        {
           // InitializeComponent();

            _defaultLayerManager = this;
            _layerProviders = new List<ILayerProviderDX>();
            _layerProviders.Add(new ShapefileLayerProviderDX()); // .shp files
            _layerProviders.Add(new ImageLayerProviderDX()); // texture files
            _layerProviders.Add(new WorldWindFileProviderDX()); // world wind tiles
            _layerProviders.Add(new WorldWindTileProviderDX());
            _layerProviders.Add(new BinaryRasterLayerProviderDX()); // .bgd files
          

            string path = System.Windows.Forms.Application.ExecutablePath;
            _layerProviderDirectories = new List<string>();
            _layerProviderDirectories.Add(System.IO.Path.GetDirectoryName(path) + "\\Plugins");
           
            
        }

        /// <summary>
        /// Creates a new instance of the LayerManager class.  A data manager is more or less
        /// just a list of data providers to use.  The very important 
        /// LayerManager.DefaultLayerManager property controls which LayerManager will be used
        /// to load data.  By default, each LayerManager sets itself as the default in its
        /// constructor.
        /// </summary>
        public LayerManagerDX(IContainer container)
        {
            container.Add(this);

            //    InitializeComponent();

            _defaultLayerManager = this;
            _layerProviders = new List<ILayerProviderDX>();
            _layerProviders.Add(new ShapefileLayerProviderDX()); // .shp files
            _layerProviders.Add(new ImageLayerProviderDX());
            _layerProviders.Add(new WorldWindFileProviderDX());
            //_layerProviders.Add(new BinaryLayerProvider()); // .bgd files
           

            string path = System.Windows.Forms.Application.ExecutablePath;
            _layerProviderDirectories = new List<string>();
            _layerProviderDirectories.Add(System.IO.Path.GetDirectoryName(path) + "\\Plugins");
           
            _preferredProviders = new Dictionary<string, ILayerProviderDX>();
            
        }

        #endregion


        #region Methods

        /// <summary>
        /// Checks a dialog filter and returns a list of just the extensions.
        /// </summary>
        /// <param name="DialogFilter">The Dialog Filter to read extensions from</param>
        /// <returns>A list of extensions</returns>
        public virtual List<string> GetSupportedExtensions(string DialogFilter)
        {
            List<string> extensions = new List<string>();
            string[] formats = DialogFilter.Split('|');
            char[] wild = {'*'};
            // We don't care about the description strings, just the extensions.
            for (int i = 1; i < formats.Length; i += 2)
            {
                // Multiple extension types are separated by semicolons
                string[] potentialExtensions = formats[i].Split(';');
                foreach (string potentialExtension in potentialExtensions)
                {
                    string ext = potentialExtension.TrimStart(wild);
                    if (extensions.Contains(ext) == false)
                    {
                        extensions.Add(ext);
                    }
                }


            }
            return extensions;

        }

      
      
        /// <summary>
        /// Opens a new layer and automatically adds it to the specified container.
        /// </summary>
        /// <param name="container">The container (usually a LayerCollection) to add to</param>
        /// <returns>The layer after it has been created and added to the container</returns>
        public virtual ILayerDX OpenLayer(ICollection<ILayer> container)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = DialogReadFilter;
            if (ofd.ShowDialog() != DialogResult.OK) return null;
            return OpenLayer(ofd.FileName, this.LoadInRam, container, this.ProgressHandler, ofd.FilterIndex);
        }

        /// <summary>
        /// Opens a set of texture tiles from the specified folder
        /// </summary>
        /// <param name="container">The container for the layer</param>
        /// <returns>A Layer</returns>
        public virtual ILayerDX OpenFolder(ICollection<ILayer> container)
        {
            FolderBrowserDialog ofd = new FolderBrowserDialog();
            if (ofd.ShowDialog() != DialogResult.OK) return null;
            return OpenFolder(ofd.SelectedPath, this.LoadInRam, container, this.ProgressHandler);

        }

        /// <summary>
        /// Opens an entire folder at once.
        /// </summary>
        /// <param name="directory"></param>
        /// <param name="container"></param>
        /// <returns></returns>
        public virtual ILayerDX OpenFolder(string directory, ICollection<ILayer> container)
        {
            return OpenFolder(directory, this.LoadInRam, container, this.ProgressHandler);
        }

        /// <summary>
        /// This launches an open file dialog and attempts to load the specified file.
        /// </summary>
        /// <param name="progressHandler">Specifies the progressHandler to receive progress messages.  This value overrides the property on this DataManager.</param>
        /// <returns>A Layer</returns>
        public virtual ILayerDX OpenLayer(IProgressHandler progressHandler)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = DialogReadFilter;
            if (ofd.ShowDialog() != DialogResult.OK) return null;
            return OpenLayer(ofd.FileName, this.LoadInRam, null, progressHandler, ofd.FilterIndex);
        }

        /// <summary>
        /// This launches an open file dialog and attempts to load the specified file.
        /// </summary>
        /// <returns>A Layer created from the file</returns>
        public virtual ILayerDX OpenLayer()
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = DialogReadFilter;
            if (ofd.ShowDialog() != DialogResult.OK) return null;
            //ofd.FilterIndex;
            return OpenLayer(ofd.FileName, this.LoadInRam, null, this.ProgressHandler, ofd.FilterIndex);
        }

       
        

        /// <summary>
        /// Attempts to call the open filename method for any ILayerProvider plugin
        /// that matches the extension on the string.
        /// </summary>
        /// <param name="filename">A String filename to attempt to open.</param>
        public virtual ILayerDX OpenLayer(string filename)
        {
            return OpenLayer(filename, this.LoadInRam, null, this.ProgressHandler, -1);
            
        }

        /// <summary>
        /// Opens a new layer and automatically adds it to the specified container.
        /// </summary>
        /// <param name="filename">A String filename to attempt to open.</param>
        /// <param name="container">The container (usually a LayerCollection) to add to</param>
        /// <returns>The layer after it has been created and added to the container</returns>
        public virtual ILayerDX OpenLayer(string filename, ICollection<ILayer> container)
        {
            return OpenLayer(filename, this.LoadInRam, container, this.ProgressHandler, -1);
        }

        /// <summary>
        /// This launches an open file dialog and attempts to load the specified file.
        /// </summary>
        /// <param name="filename">A String filename to attempt to open.</param>
        /// <param name="progressHandler">Specifies the progressHandler to receive progress messages.  This value overrides the property on this DataManager.</param>
        /// <returns>A Layer</returns>
        public virtual ILayerDX OpenLayer(string filename, IProgressHandler progressHandler)
        {
            return OpenLayer(filename, this.LoadInRam, null, progressHandler, -1);
        }

        /// <summary>
        /// Opens a folder with the expectation that the files are tiled textures
        /// </summary>
        /// <param name="directory">The string directory</param>
        /// <param name="inRam">This likely is ignored</param>
        /// <param name="container">The mapframe to open the new layer into</param>
        /// <param name="progressHandler">Where to show progress messages</param>
        /// <returns></returns>
        public virtual ILayerDX OpenFolder(string directory, bool inRam, ICollection<ILayer> container, IProgressHandler progressHandler)
        {
            // To Do: Add Customization that allows users to specify which plugins to use in priority order.
            ILayerDX result;

            // First check for the extension in the preferred plugins list

            
            foreach (ILayerProviderDX dp in _layerProviders)
            {
                if (dp.DialogReadFilter == null)
                {
                    // attempt to open with the filename.
                    result = dp.OpenLayer(directory, inRam, container, progressHandler);
                    if (result != null)
                    {
                        return result;

                    }
                }
               
            }

            throw new ApplicationException(MapWindow.DirectX.MessageStrings3D.FileTypeNotSupported);
        }

        /// <summary>
        /// Attempts to call the open filename method for any ILayerProvider plugin
        /// that matches the extension on the string.
        /// </summary>
        /// <param name="filename">A String filename to attempt to open.</param>
        /// <param name="inRam">A boolean value that if true will attempt to force a load of the data into memory.  This value overrides the property on this LayerManager.</param>
        /// <param name="container">A container to open this layer in</param>
        /// <param name="progressHandler">Specifies the progressHandler to receive progress messages.  This value overrides the property on this LayerManager.</param>
        /// <param name="filterIndex">The index in the open file dialog filter</param>
        /// <returns>An ILayer</returns>
        public virtual ILayerDX OpenLayer(string filename, bool inRam, ICollection<ILayer> container, IProgressHandler progressHandler, int filterIndex)
        {
            // To Do: Add Customization that allows users to specify which plugins to use in priority order.
            ILayerDX result;

            // First check for the extension in the preferred plugins list

            if (filterIndex != -1)
            {
                ILayerProviderDX lp = ProviderFromReadFilterIndex(filterIndex);
                if (lp != null)
                {
                    return lp.OpenLayer(filename, inRam, container, progressHandler);
                   
                }
            }

            string ext = System.IO.Path.GetExtension(filename);
           
            // Then check the general list of developer specified providers... but not the directory providers
          
            foreach (ILayerProviderDX dp in _layerProviders)
            {
                if (dp.DialogReadFilter != null)
                {
                    if (GetSupportedExtensions(dp.DialogReadFilter).Contains(ext))
                    {
                        // attempt to open with the filename.
                        result = dp.OpenLayer(filename, inRam, container, progressHandler);
                        if (result != null)
                        {
                            return result;

                        }
                    }
                }
            }

            throw new ApplicationException(MapWindow.DirectX.MessageStrings3D.FileTypeNotSupported);

        }


        #endregion

        #region Properties

        

        // May make this invisible if we can
        /// <summary>
        /// Gets or sets the list of ILayerProviders that should be used in the project.
        /// </summary>
        [Browsable(false),
         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual List<ILayerProviderDX> LayerProviders
        {
            get { return _layerProviders; }
            set 
            {
                _layerProviders = value; 
            }
        }



        /// <summary>
        /// Gets or sets the path (either as a full path or as a path relative to
        /// the MapWindow.dll) to search for plugins that implement the ILayerProvider interface.
        /// </summary>
        [Category("Providers"),
         Description("Gets or sets the list of string path names that should be used to search for ILayerProvider interfaces.")]
        public virtual List<string> LayerProviderDirectories
        {
            get { return _layerProviderDirectories; }
            set { _layerProviderDirectories = value; }
        }

        


        /// <summary>
        /// Gets or sets the dialog read filter to use for opening data files.
        /// </summary>
        [Category("Filters"),
         Description("Gets or sets the string that should be used when using this data manager is used to open files.")]
        public virtual string DialogReadFilter
        {
            get
            {
                // The developer can bypass the default behavior simply by caching something here.
                if (_dialogReadFilter != null) return _dialogReadFilter;

                string result;
                List<string> extensions = new List<string>();

                List<List<string>> providerExtensions = new List<List<string>>();

                foreach (ILayerProviderDX dp in _layerProviders)
                {
                    if (dp.DialogReadFilter != null)
                    {
                        string[] formats = dp.DialogReadFilter.Split('|');
                        List<string> currentProviderExtensions = new List<string>();
                        // We don't care about the description strings, just the extensions.
                        for (int i = 1; i < formats.Length; i += 2)
                        {
                            // Multiple extension types are separated by semicolons
                            string[] potentialExtensions = formats[i].Split(';');
                            foreach (string potentialExtension in potentialExtensions)
                            {
                                currentProviderExtensions.Add(potentialExtension);
                                if (extensions.Contains(potentialExtension) == false)
                                {
                                    extensions.Add(potentialExtension);
                                }
                            }
                        }
                        providerExtensions.Add(currentProviderExtensions);
                    }
                }
                
                // We now have a list of all the file extensions supported
                result = "All Supported Formats|" + String.Join(";", extensions.ToArray());
               
                int ind = 0;
                // Now add each of the individual lines, prepended with the provider name
                foreach (ILayerProviderDX dp in _layerProviders)
                {
                    if (dp.DialogReadFilter != null)
                    {
                        result += "|" + dp.Name + " |" + String.Join(";", providerExtensions[ind].ToArray());
                        ind++;
                    }
                }
                result += "|All Files (*.*) |*.*";
                return result;
            }
            set { _dialogReadFilter = value; }
        }

        /// <summary>
        /// Obtains the provider that corresponds to a filter index in the read dialog filter
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public virtual ILayerProviderDX ProviderFromReadFilterIndex(int index)
        {
           

            // the 0 index (all formats) is the same as not specifying a provider.
            if (index <= 1) return null;

            // the last index value in the dialog is for all files, which is not a provider.
            if (index > _layerProviders.Count + 1) return null;

            return _layerProviders[index - 2];
        }

        /// <summary>
        /// Gets or sets the dialog write filter to use for saving data files.
        /// </summary>
        [Category("Filters"),
         Description("Gets or sets the string that should be used when this data manager is used to save files.")]
        public virtual string DialogWriteFilter
        {
            get
            {
                // The developer can bypass the default behavior simply by caching something here.
                if (_dialogWriteFilter != null) return _dialogWriteFilter;

                string result;
                List<string> extensions = new List<string>();

                List<List<string>> providerExtensions = new List<List<string>>();

                foreach (ILayerProviderDX dp in _layerProviders)
                {
                    string[] formats = dp.DialogWriteFilter.Split('|');
                    List<string> currentProviderExtensions = new List<string>();
                    // We don't care about the description strings, just the extensions.
                    for (int i = 1; i < formats.Length; i += 2)
                    {
                        // Multiple extension types are separated by semicolons
                        string[] potentialExtensions = formats[i].Split(';');
                        foreach (string potentialExtension in potentialExtensions)
                        {
                            currentProviderExtensions.Add(potentialExtension);
                            if (extensions.Contains(potentialExtension) == false)
                            {
                                extensions.Add(potentialExtension);
                            }
                        }
                    }
                    providerExtensions.Add(currentProviderExtensions);
                }

                // We now have a list of all the file extensions supported
                result = "All Supported Formats|" + String.Join(";", extensions.ToArray());

                int ind = 0;
                // Now add each of the individual lines, prepended with the provider name
                foreach (ILayerProviderDX dp in _layerProviders)
                {
                    result += "|" + dp.Name + " |" + String.Join(";", providerExtensions[ind].ToArray());
                }
                result += "|All Files (*.*) |*.*";
                return result;
            }
            set { _dialogWriteFilter = value; }
        }


        

       

      




        /// <summary>
        /// Sets the default condition for how this data manager should try to load layers.
        /// This will be overridden if the inRam property is specified as a parameter.
        /// </summary>
        [Category("Behavior"),
         Description("Gets or sets the default condition for subsequent load operations which may be overridden by specifying inRam in the parameters.")]
        public bool LoadInRam
        {
            get { return _loadInRam; }
            set { _loadInRam = value; }
        }


        /// <summary>
        /// Gets or sets a dictionary of ILayerProviders with corresponding extensions.  The 
        /// standard order is to try to load the data using a PreferredProvider.  If that
        /// fails, then it will check the list of dataProviders, and finally, if that fails,
        /// it will check the plugin Layer Providers in directories.
        /// </summary>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual System.Collections.Generic.Dictionary<string, ILayerProviderDX> PreferredProviders
        {
            get { return _preferredProviders; }
            set { _preferredProviders = value; }
        }

        /// <summary>
        /// Gets or sets a progress handler for any open operations that are intiated by this 
        /// LayerManager and don't override this value with an IProgressHandler specified in the parameters.
        /// </summary>
        [Category("Handlers"),
         Description("Gets or sets the object that implements the IProgressHandler interface for recieving status messages."),
         TypeConverter(typeof(mwStatusStrip))] // if the designer wants something here, should work.
        public virtual IProgressHandler ProgressHandler
        {
            get { return _progressHandler; }
            set { _progressHandler = value; }
        }
        
      

        #endregion

        #region Events

        /// <summary>
        /// Occurs after the directory providers have been loaded into the project.
        /// </summary>
        public virtual event EventHandler DirectoryProvidersLoaded;

        /// <summary>
        /// Triggers the DirectoryProvidersLoaded event
        /// </summary>
        protected virtual void OnProvidersLoaded(List<ILayerProviderDX> list)
        {
            if (DirectoryProvidersLoaded != null)
            {
                DirectoryProvidersLoaded(this, new EventArgs());
            }
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }


        /// <summary>
        /// This should be called once all the permitted directories have been set in the code.
        /// This will not affect the PreferredProviders or the general list of Providers.
        /// These automatically have the lowest priority and will only be used if nothing
        /// else works.  Use the PreferredProviders to force preferential loading of 
        /// a plugin LayerProvider.
        /// </summary>
        /// <returns>A list of just the newly added LayerProviders from this method.</returns>
        public virtual List<ILayerProviderDX> LoadProvidersFromDirectories()
        {

            Type[] CoClassList;
            Type[] InfcList;
            System.Reflection.Assembly asm;
            List<ILayerProviderDX> result = new List<ILayerProviderDX>();
            foreach (string directory in _layerProviderDirectories)
            {
                foreach (string file in System.IO.Directory.GetFiles(directory, "*.dll", System.IO.SearchOption.AllDirectories))
                {
                    if (file.Contains("Interop")) continue;
                    if (System.IO.Path.GetFileName(file) == "MapWindow.dll") continue; // If they forget to turn "copy local" to false.

                    asm = System.Reflection.Assembly.LoadFrom(file);
                    try
                    {
                        CoClassList = asm.GetTypes();
                        foreach (Type CoClass in CoClassList)
                        {
                            InfcList = CoClass.GetInterfaces();

                            foreach (Type Infc in InfcList)
                            {

                                try
                                {
                                    object obj = asm.CreateInstance(CoClass.FullName);
                                    ILayerProviderDX dp = obj as ILayerProviderDX;
                                    if (dp != null)
                                    {
                                        _layerProviders.Add(dp);
                                        result.Add(dp);
                                    }
                                }
                                catch
                                {
                                    // this object didn't work, but keep looking
                                }

                            }
                        }
                    }
                    catch
                    {
                        // We will fail frequently.
                    }
                }
            }
            OnProvidersLoaded(result);
            return result;

        }

        /// <summary>
        /// Given a string filename for the "*.dll" file, this will attempt to load any classes that implement the
        /// ILayerProvder interface.
        /// </summary>
        /// <param name="filename">The string path of the assembly to load from.</param>
        /// <returns>A list that contains only the providers that were just loaded.  This may be a list of count 0, but shouldn't return null.</returns>
        public virtual List<ILayerProviderDX> LoadProvidersFromAssembly(string filename)
        {
            
            Type[] CoClassList;
            Type[] InfcList;
            System.Reflection.Assembly asm;
            List<ILayerProviderDX> result = new List<ILayerProviderDX>();
            if (System.IO.Path.GetExtension(filename) != ".dll") return result;
            if (filename.Contains("Interop")) return result;

            asm = System.Reflection.Assembly.LoadFrom(filename);
            try
            {
                CoClassList = asm.GetTypes();
                foreach (Type CoClass in CoClassList)
                {
                    InfcList = CoClass.GetInterfaces();

                    foreach (Type Infc in InfcList)
                    {

                        try
                        {
                            object obj = asm.CreateInstance(CoClass.FullName);
                            ILayerProviderDX dp = obj as ILayerProviderDX;
                            if (dp != null)
                            {
                                _layerProviders.Add(dp);
                                result.Add(dp);
                            }
                        }
                        catch
                        {
                            // this object didn't work, but keep looking
                        }

                    }
                }
            }
            catch
            {
                // We will fail frequently.
            }
               
            OnProvidersLoaded(result);
            return result;
        }

      

        #endregion
    }
}
